import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class DistanceMatrixCalculator
{
	private final static Logger LOGGER = MyLogger.getLogger();

	private final File _inputDirectory;
	private File _outputDirectory;
	private Toolset _toolset;

	private double[][] _distances;
	private String[] _distanceLabels;
	private int[] _distanceClasses;

	/** separator for output csv-file */
	private static final String CSV_SEP = ",";

	public DistanceMatrixCalculator(Toolset toolset, File inputDirectory, File outputDirectory) throws IOException
	{
		this._inputDirectory = inputDirectory;
		this._outputDirectory = outputDirectory;
		this._toolset = toolset;

		// for each file
		// calc code matrix
		// calc distance to all files
	}

	/**
	 * Calculates & saves the iris codes for all files in given input folder
	 * 
	 * @return
	 * @throws IOException
	 * @throws InterruptedException
	 */
	public void calcAllIrisCodes() throws IOException, InterruptedException
	{
		File irisCodeOutput = new File(_outputDirectory, _toolset.getID() + "/");
		irisCodeOutput.mkdirs();

		List<File> irisCodeList = new ArrayList<File>();
		getFileList(_inputDirectory.getAbsolutePath(), irisCodeList);

		LOGGER.info("Found " + irisCodeList.size() + " files...");

		for (File file : irisCodeList)
		{
			String path = _inputDirectory.toURI().relativize(file.toURI()).toString();
			String[] parts = path.split("\\.");

			_toolset.calcIrisCode(file, new File(irisCodeOutput, parts[0] + ".png"));
			LOGGER.info("calculating: " + parts[0]);
		}
	}

	/**
	 * @brief Calculates the Hamming distance of given iris codes to each other
	 * 
	 *        This method requires the Iris-Codes to be calculated and stored in the working directory. Between all these codes, which are labelled according to their source-images, the distance to each other is calculated.
	 * 
	 *        If n is the number of iris codes in a directory, then this results in an n x n Matrix, each cell containing a single value which describes the distance between the iris code represented by the column and this represented by the row. In conclusion, elements in the first diagonal (where column=row) have "zero"-distance, since this is the distance of an iris code to itself.
	 * 
	 *        Calculation of distance: The method calcDistance of a toolkit is called, which returns the distance between to iris codes as a double
	 * 
	 *        Note that depending on the used toolset (FeatureExtractor, Iris-Code, Distance-Calculator) the "zero"-distance is not always zero, but some other fixed value.
	 * 
	 *        The distance matrix is stored in the member _distances, from where it can be written to storage via saveToCSV-method
	 * 
	 * @throws IOException
	 *                   If something goes wrong when searching for the iris-codes
	 * @throws InterruptedException
	 *                   If something goes wrong while the Distance-Calculator is called (external application)
	 */
	public void calcAndStoreDistanceMatrix() throws IOException, InterruptedException
	{
		File codeOutputBase = new File(_outputDirectory, _toolset.getID() + "/");

		if (!codeOutputBase.exists())
		{
			LOGGER.warning("You haven't calculated the iris codes, at least the code-directory " + codeOutputBase + " does not exist!");
		}

		List<File> codes = new ArrayList<File>();

		initializeDistanceArrays(codeOutputBase, codes);
		fillClassesAndLables(codes);

		int x = 0;
		int y = 0;

		for (File file1 : codes)
		{
			long start = System.currentTimeMillis();
			LOGGER.info("Started  calculating for " + file1);

			y = 0;

			for (File file2 : codes)
			{
				_distances[x][y] = _toolset.calcDistance(file1, file2);
				y++;
			}

			x++;

			LOGGER.info("Finished. Calculating tooked " + (System.currentTimeMillis() - start) + " millis");
		}

		saveToCSV(new File(_toolset.getID() + "_subset3.csv"));
	}

	private void initializeDistanceArrays(File codeOutputBase, List<File> codes)
	{
		getFileList(codeOutputBase.getAbsolutePath(), codes);

		_distances = new double[codes.size()][codes.size()];
		_distanceClasses = new int[codes.size()];
		_distanceLabels = new String[codes.size()];
	}

	private void fillClassesAndLables(List<File> codes)
	{
		int c = -1;
		int i = 0;

		for (File f : codes)
		{
			String name = f.getName();

			Pattern pattern = Pattern.compile("S1(.*)[RL]");
			Matcher matcher = pattern.matcher(name);

			if (matcher.find())
			{
				c = Integer.parseInt(matcher.group(1));
			}
			else
			{
				LOGGER.warning("Parsing error of code-file with name " + name);
				continue;
			}

			_distanceClasses[i] = c;
			_distanceLabels[i] = name;
			i++;
		}
	}

	public void readFileFromConsoleOutput()
	{
		BufferedReader bufferedReader = null;
		String line;
		int size = 2639;

		_distances = new double[size][size];
		_distanceClasses = new int[size];
		_distanceLabels = new String[size];

		int x = 0;
		int y = 0;
		int c = -1;
		String lastClass = "";
		String[] pieces = new String[3];

		try
		{
			bufferedReader = new BufferedReader(new FileReader("output.txt"));

			while ((line = bufferedReader.readLine()) != null)
			{
				pieces = line.split(" ");

				if (!lastClass.equals(pieces[0]) && lastClass.length() > 0)
				{
					x++;
					y = 0;
				}

				Pattern pattern = Pattern.compile("S1(.*)[RL]");
				Matcher matcher = pattern.matcher(pieces[0]);

				if (matcher.find())
				{
					c = Integer.parseInt(matcher.group(1));
				}
				else
				{
					LOGGER.warning("Parsing error of code-file with name " + pieces[0]);
					continue;
				}

				_distanceClasses[x] = c;
				_distanceLabels[x] = pieces[0];
				_distances[x][y] = Double.parseDouble(pieces[2]);
				y++;

				lastClass = pieces[0];
			}

			saveToCSV(new File(_toolset.getID() + "_subset3.csv"));
		}
		catch (Exception e)
		{
			System.out.println(pieces.toString());
			e.printStackTrace();
		}
		finally
		{
			try
			{
				bufferedReader.close();
			}
			catch (IOException e)
			{
				e.printStackTrace();
			}
		}
	}

	/**
	 * @brief Saves the formerly calculated Distance-Matrix _distances to storage.
	 * 
	 *        After the Distance-Matrix is calculated by the calcDistMatrix()-method, this can be used to save the matrix to a CSV-File, which may be used for storing intermediate results and/or further processing of the matrix in other programs, such as Matlab.
	 * 
	 *        The n x n Matrix _distances is written to a file. In this process each row and column are labelled with their representational file-names and class numbers. The class number is the id of the person, to whom the source image belongs to. Note that images of the left and right eye are mapped to the same class (namely the person-ID) here. The filename corresponds to the source filename. (Sub-)folders and storage location are not contained in the labelling.
	 * 
	 * @param target
	 *                  Location, where the CSV should be written to.
	 * @throws IOException
	 *                   If something goes wrong while writing the frame
	 */
	private void saveToCSV(File target) throws IOException
	{
		FileWriter outFile = new FileWriter(target);
		PrintWriter out = new PrintWriter(outFile);

		for (int y = 0; y < _distanceClasses.length + 2; y++)
		{
			for (int x = 0; x < _distanceClasses.length + 2; x++)
			{
				if (y >= 2)
				{
					if (x == 0)
					{
						out.print(_distanceLabels[y - 2] + CSV_SEP);
					}
					else if (x == 1)
					{
						out.print(_distanceClasses[y - 2] + CSV_SEP);
					}
					else
					{
						out.print(_distances[x - 2][y - 2] + CSV_SEP);
					}
				}
				else
				{
					if (y == 0)
					{
						if (x < 2)
						{
							out.print(CSV_SEP);
						}
						else
						{
							out.print(_distanceLabels[x - 2] + CSV_SEP);
						}
					}
					else if (y == 1)
					{
						if (x < 2)
						{
							out.print(CSV_SEP);
						}
						else
						{
							out.print(_distanceClasses[x - 2] + CSV_SEP);
						}
					}
					else
					{
						System.err.print("Should never reached this..");
					}
				}
			}

			out.println(); // line break
		}

		out.flush();
		out.close();
	}

	// get all the files from a directory
	private void getFileList(String directoryName, List<File> files)
	{
		File directory = new File(directoryName);

		File[] fList = directory.listFiles();

		for (File file : fList)
		{
			if (file.isFile())
			{
				files.add(file);
			}
			else if (file.isDirectory())
			{
				getFileList(file.getAbsolutePath(), files);
			}
		}
	}

}
